﻿using System;
using System.Threading;

namespace InfiniTec.Threading
{
	public sealed class SpinWaitLock: IDisposable
	{
		private const Int32 FreeStateValue = 0;
		private const Int32 OwnedStateValue = 1;
		private static readonly Boolean IsSingleCpuMachine = (Environment.ProcessorCount == 1);
		private Int32 _LockState; // Defaults to 0=FreeStateValue


		public void Enter()
		{
			Thread.BeginCriticalRegion();
			while (true)
			{
				// If resource available, set it to in-use and return
				if (Interlocked.Exchange(
						ref _LockState, OwnedStateValue) == FreeStateValue)
				{
					return;
				}

				// Efficiently spin, until the resource looks like it might 
				// be free. NOTE: Just reading here (as compared to repeatedly 
				// calling Exchange) improves performance because writing 
				// forces all CPUs to update this value
				while (Thread.VolatileRead(ref _LockState) == OwnedStateValue)
				{
					StallThread();
				}
			}
		}
		public bool TryEnter(TimeSpan timeout)
		{
			DateTime end = DateTime.Now + timeout;

			Thread.BeginCriticalRegion();
			while (DateTime.Now < end)
			{
				// If resource available, set it to in-use and return
				if (Interlocked.Exchange(
				    	ref _LockState, OwnedStateValue) == FreeStateValue)
				{
					return true;
				}

				// Efficiently spin, until the resource looks like it might 
				// be free. NOTE: Just reading here (as compared to repeatedly 
				// calling Exchange) improves performance because writing 
				// forces all CPUs to update this value
				while (Thread.VolatileRead(ref _LockState) == OwnedStateValue)
				{
					StallThread();
				}
			}
			Thread.EndCriticalRegion();
			return false;
			
		}

		public void Exit()
		{
			// Mark the resource as available
			Interlocked.Exchange(ref _LockState, FreeStateValue);
			Thread.EndCriticalRegion();
		}

		private static void StallThread()
		{
			if (IsSingleCpuMachine)
			{
				// On a single-CPU system, spinning does no good
				SafeNativeMethods.SwitchToThread();
			}
			else
			{
				// Multi-CPU system might be hyper-threaded, let other thread run
				Thread.SpinWait(1);
			}
		}

		void IDisposable.Dispose()
		{
			Exit();
		}
	}
}